#ifndef YELLOWPAGE_H_
#define YELLOWPAGE_H_

#include <list>

#include "conf/node.h"
#include "conf/attribute.h"
#include "conf/collection.h"
#include "loader.h"
#include "favorite.h"

namespace gpyp {

class Application;

class ID {
public:
  typedef unsigned int block_t;
  static const size_t   BLOCK_SIZE = sizeof(block_t);
  static const size_t   LENGTH_STRING = 32;
  static const size_t   LENGTH_STRING_BLOCK = BLOCK_SIZE * 2;
  static const size_t   LENGTH = LENGTH_STRING / LENGTH_STRING_BLOCK;
  static const char    *ID_INVALID;

private:
  block_t data_[LENGTH];
  bool valid_;

  inline void reset() {
    memset(data_, 0, sizeof(data_));
    valid_ = false;
  }

public:
  inline bool equal(const ID &id) const {
    if (valid() && id.valid()) {
      return memcmp(data_, id.data_, sizeof(data_)) == 0;
    } else {
      return false;
    }
  }
  inline bool operator==(const ID &id) const {
    return equal(id);
  }
  inline bool operator!=(const ID &id) const {
    return !equal(id);
  }

  inline block_t operator[](int idx) const {
    if (idx < 0) idx += LENGTH;
    return data_[idx];
  }

  void set(const std::string &id_str);
  inline void set(const ID &id) {
    memcpy(data_, id.data_, sizeof(data_));
    valid_ = id.valid_;
  }
  inline const ID &operator=(const ID &id) {
    set(id);
    return *this;
  }

  inline bool valid() const {
    return valid_;
  }
  inline operator bool() const {
    return valid_;
  }
  inline bool operator!() const {
    return !valid_;
  }

  inline ID() {
    reset();
  }
  inline ID(const std::string &id_str) {
    set(id_str);
  }
  inline ID(const ID &id) {
    set(id);
  }
};

inline std::ostream &operator<<(std::ostream &os, const ID &id) {
  if (id) {
    char str[ID::LENGTH_STRING + 1];
#define CHR2HEX(HB) ((HB)+((HB)>9?'A'-10:'0'))
    for(size_t i = 0; i < ID::LENGTH; ++i) {
      ID::block_t b = id[i];
      char *p_str = str + (ID::LENGTH_STRING_BLOCK * i);
      for(size_t j = 0; j < ID::LENGTH_STRING_BLOCK; ++j) {
        p_str[ID::LENGTH_STRING_BLOCK - j - 1] = CHR2HEX(b & 0xf);
        b >>= 4;
      }
    }
#undef CHR2HEX
    str[ID::LENGTH_STRING] = '\0';
    os << str;
  } else {
    os << ID::ID_INVALID;
  }
  return os;
}

class YellowPage;
class Channel;
typedef boost::shared_ptr<Channel> RefChannel;

class Channel : boost::noncopyable {
  friend class YellowPage;
  friend class YPTXTParser;
  friend class YPXMLParserBase;
public:
  enum Flag {
    FLAG_NONE          = 0,
    FLAG_NEW           = 1 << 0,
    FLAG_CHANGED       = 1 << 1,
    FLAG_RETURNED      = 1 << 2,
    FLAG_NOTIFIED      = 1 << 3,
    FLAG_AUTO_PLAYED   = 1 << 4,
    FLAG_ENDED         = 1 << 5,
  };

  typedef const Glib::ustring &(Channel::*PropertyFuncStr)() const;
  typedef int (Channel::*PropertyFuncInt)() const;
  struct Property {
    const char *name;
    const PropertyFuncStr func_str;
    const PropertyFuncInt func_int;
  };

  static const Property  _prop_line;
  static const Property  _prop_rank;
  static const Property  _prop_name;
  static const Property  _prop_id;
  static const Property  _prop_tip;
  static const Property  _prop_contact;
  static const Property  _prop_genre;
  static const Property  _prop_desc;
  static const Property  _prop_listeners;
  static const Property  _prop_relays;
  static const Property  _prop_bitrate;
  static const Property  _prop_type;
  static const Property  _prop_track_artist;
  static const Property  _prop_track_album;
  static const Property  _prop_track_title;
  static const Property  _prop_track_contact;
  static const Property  _prop_name_url;
  static const Property  _prop_age;
  static const Property  _prop_status;
  static const Property  _prop_comment;
  static const Property  _prop_ypname;
  static const Property  _prop_ypurl;
  static const Property *_properties[];

  static const Property *get_property(const Glib::ustring& name);

private:
  static const char   *INVALIDINTVALUE;

  Application   &app_;
  YellowPage    *yp_;

  boost::posix_time::ptime time_created_;
  boost::posix_time::ptime time_last_played_;
  boost::posix_time::ptime time_ended_;

  Glib::ustring line_;
  int           rank_;
  Glib::ustring rank_str_;
  Glib::ustring name_;
  ID            id_;
  Glib::ustring id_str_;
  Glib::ustring tip_;
  Glib::ustring contact_;
  Glib::ustring genre_;
  Glib::ustring desc_;
  int           listeners_;
  Glib::ustring listeners_str_;
  int           relays_;
  Glib::ustring relays_str_;
  int           bitrate_;
  Glib::ustring bitrate_str_;
  Glib::ustring type_;
  Glib::ustring track_artist_;
  Glib::ustring track_album_;
  Glib::ustring track_title_;
  Glib::ustring track_contact_;
  Glib::ustring name_url_;
  int           age_;
  Glib::ustring age_str_;
  Glib::ustring status_;
  Glib::ustring comment_;

  Glib::ustring __dummy_string__;

  RefFavoriteMatch match_;

  uint          flags_;

public:
  inline YellowPage *get_yp() {
    return yp_;
  }

  inline const boost::posix_time::ptime &get_time_created() {
    return time_created_;
  }
  inline const boost::posix_time::ptime &get_time_last_played() {
    return time_last_played_;
  }
  inline const boost::posix_time::ptime &get_time_ended() {
    return time_ended_;
  }

  inline const Glib::ustring &get_line() const {
    return line_;
  }
  inline int get_rank() const {
    return rank_;
  }
  inline const Glib::ustring &get_rank_str() const {
    return rank_str_;
  }
  inline const Glib::ustring &get_name() const {
    return name_;
  }
  inline const ID &get_id() const {
    return id_;
  }
  inline const Glib::ustring &get_id_str() const {
    return id_str_;
  }
  inline const Glib::ustring &get_tip() const {
    return tip_;
  }
  inline const Glib::ustring &get_contact() const {
    return contact_;
  }
  inline const Glib::ustring &get_genre() const {
    return genre_;
  }
  inline const Glib::ustring &get_desc() const {
    return desc_;
  }
  inline int get_listeners() const {
    return listeners_;
  }
  inline const Glib::ustring &get_listeners_str() const {
    return listeners_str_;
  }
  inline int get_relays() const {
    return relays_;
  }
  inline const Glib::ustring &get_relays_str() const {
    return relays_str_;
  }
  inline int get_bitrate() const {
    return bitrate_;
  }
  inline const Glib::ustring &get_bitrate_str() const {
    return bitrate_str_;
  }
  inline const Glib::ustring &get_type() const {
    return type_;
  }
  inline const Glib::ustring &get_track_artist() const {
    return track_artist_;
  }
  inline const Glib::ustring &get_track_album() const {
    return track_album_;
  }
  inline const Glib::ustring &get_track_title() const {
    return track_title_;
  }
  inline const Glib::ustring &get_track_contact() const {
    return track_contact_;
  }
  inline const Glib::ustring &get_name_url() const {
    return name_url_;
  }
  inline int get_age() const {
    return age_;
  }
  inline const Glib::ustring &get_age_str() const {
    return age_str_;
  }
  inline const Glib::ustring &get_status() const {
    return status_;
  }
  inline const Glib::ustring &get_comment() const {
    return comment_;
  }

  const Glib::ustring &get_ypname() const;
  const Glib::ustring &get_ypurl() const;

  void rematch_favorite();
  inline RefFavoriteMatch get_favorite_match() const {
    return match_;
  }

  inline uint get_flags() const {
    return flags_;
  }

#define FLAG_PROP_GETTER(name, NAME)            \
  inline bool get_is_##name() {                 \
    return (flags_ & FLAG_##NAME) != 0;         \
  }
#define FLAG_PROP_SETTER(name, NAME)            \
  inline void set_is_##name(bool is_##name) {   \
    if (is_##name) {                            \
      flags_ |= FLAG_##NAME;                    \
    } else {                                    \
      flags_ &= ~FLAG_##NAME;                   \
    }                                           \
  }
#define FLAG_PROP(name, NAME)                   \
  FLAG_PROP_GETTER(name, NAME)                  \
  FLAG_PROP_SETTER(name, NAME)

  FLAG_PROP(new,         NEW)
  FLAG_PROP(returned,    RETURNED)
  FLAG_PROP(changed,     CHANGED)
  FLAG_PROP(notified,    NOTIFIED)
  FLAG_PROP(auto_played, AUTO_PLAYED)
  FLAG_PROP_GETTER(ended, ENDED)

#undef FLAG_PROP_GETTER
#undef FLAG_PROP_SETTER
#undef FLAG_PROP

  inline void set_is_ended(bool ended) {
    if (ended) {
      if (!(flags_ & FLAG_ENDED)) {
        flags_ |= FLAG_ENDED;
        time_ended_ = boost::posix_time::second_clock::local_time();
      }
    } else {
      if (flags_ & FLAG_ENDED) {
        flags_ &= ~FLAG_ENDED;
        time_ended_ = boost::posix_time::not_a_date_time;
      }
    }
  }

  inline void set_played() {
    time_last_played_ = boost::posix_time::second_clock::local_time();
  }

private:
  void set_previous(RefChannel ch);

public:
  Channel(Application &app, YellowPage *yp, int rank);
};

template<typename T>
class IDHashTable : boost::noncopyable {
public:
  typedef sigc::slot<void, const ID &, boost::shared_ptr<T> > SlotForeach;

private:
  GHashTable *hash_table_;

  static guint hash_func(gconstpointer key) {
    return (*(const ID *)key)[-1];
  }
  static gboolean key_equal_func(gconstpointer a, gconstpointer b) {
    return (*(const ID *)a) == (*(const ID *)b);
  }
  static void key_destroy_func(gpointer data) {
    delete (ID *)data;
  }
  static void value_destroy_func(gpointer data) {
    delete (boost::shared_ptr<T> *)data;
  }

  inline static GHashTable *create() {
    return g_hash_table_new_full(&IDHashTable<T>::hash_func,
                                 &IDHashTable<T>::key_equal_func,
                                 &IDHashTable<T>::key_destroy_func,
                                 &IDHashTable<T>::value_destroy_func);
  }
  inline static void destroy(GHashTable *hash_table) {
    g_hash_table_destroy(hash_table);
  }

  static void foreach_func(gpointer key,
                           gpointer value,
                           gpointer user_data) {
    (*(SlotForeach *)user_data)(*(const ID *)key,
                                *(boost::shared_ptr<T> *)value);
  }

public:
  inline void insert(const ID &id, boost::shared_ptr<T> value) {
    if (id) {
      boost::shared_ptr<T> *val = new boost::shared_ptr<T>(value);
      g_hash_table_insert(hash_table_, new ID(id), val);
    }
  }
  inline boost::shared_ptr<T> lookup(const ID &id) const {
    if (id) {
      gpointer p = g_hash_table_lookup(hash_table_, &id);
      if (p) return *(boost::shared_ptr<T> *)p;
    }
    return boost::shared_ptr<T>();
  }
  inline bool erase(const ID &id) {
    return g_hash_table_remove(hash_table_, &id);
  }
  inline void clear() {
    destroy(hash_table_);
    hash_table_ = create();
  }

  inline void foreach(SlotForeach slot) const {
    g_hash_table_foreach(hash_table_,
                         &IDHashTable<T>::foreach_func,
                         &slot);
  }

  inline guint size() const {
    return g_hash_table_size(hash_table_);
  }

  inline void swap(IDHashTable<T> &hash_table) {
    GHashTable *tmp = hash_table.hash_table_;
    hash_table.hash_table_ = hash_table_;
    hash_table_ = tmp;
  }

  inline IDHashTable() :
    hash_table_(create())
  {
  }
  inline ~IDHashTable() {
    destroy(hash_table_);
  }
};

class YPParser : boost::noncopyable {
protected:
  Application &app_;
  YellowPage  &yp_;

  std::list<RefChannel> &channel_list_;
  IDHashTable<Channel>  &hash_table_;

  void add_channel(RefChannel ch);

  YPParser(Application &app, YellowPage &yp,
           std::list<RefChannel> &channel_list,
           IDHashTable<Channel>  &hash_table);
public:
  virtual ~YPParser();
};

class YPTXTParser : public YPParser {
private:
  typedef Glib::ustring Channel::*Column;
  static const Column _columns[];

  static const char SPLITTER[];

public:
  void parse(const conf::Source &source);

  YPTXTParser(Application &app, YellowPage &yp,
              std::list<RefChannel> &channel_list,
              IDHashTable<Channel>  &hash_table);
};

class YPXMLParserBase : public conf::StackParser {
protected:
  RefChannel ch_;

private:
#define SET_CH_INT(NAME) {                              \
    ch_->NAME##_ = toint(value, -1);                    \
    if (ch_->NAME##_ < 0) {                             \
      ch_->NAME##_str_ = Channel::INVALIDINTVALUE;      \
    } else {                                            \
      ch_->NAME##_str_ = value;                         \
    }                                                   \
  }
  inline void onattr_ch_name(const char *value) {
    ch_->name_    = value;
    ch_->name_url_ = URL::encode(ch_->name_);
  }
  inline void onattr_ch_id(const char *value) {
    ch_->id_str_ = value;
    ch_->id_.set(ch_->id_str_);
  }
  inline void onattr_ch_tip(const char *value) {
    ch_->tip_ = value;
  }
  inline void onattr_ch_genre(const char *value) {
    ch_->genre_ = value;
  }
  inline void onattr_ch_listeners(const char *value) {
    SET_CH_INT(listeners);
  }
  inline void onattr_ch_relays(const char *value) {
    SET_CH_INT(relays);
  }
  inline void onattr_ch_bitrate(const char *value) {
    SET_CH_INT(bitrate);
  }
  inline void onattr_ch_type(const char *value) {
    ch_->type_ = value;
  }
  inline void onattr_ch_age(const char *value) {
    ch_->age_ = toint(value, -1);
    if (ch_->age_ < 0) {
      ch_->age_str_ = "--:--";
    } else {
      ch_->age_str_ = format("%i:%02i", ch_->age_ / 60, ch_->age_ % 60).c_str();
    }
  }
  inline void onattr_ch_contact(const char *value) {
    ch_->contact_ = value;
  }

  inline void onattr_track_title(const char *value) {
    ch_->track_title_ = value;
  }
  inline void onattr_track_artist(const char *value) {
    ch_->track_artist_ = value;
  }

  inline void onattr_relay_listeners(const char *value) {
    SET_CH_INT(listeners);
  }
  inline void onattr_relay_relays(const char *value) {
    SET_CH_INT(relays);
  }
  inline void onattr_relay_status(const char *value) {
    ch_->status_ = value;
  }
#undef SET_CH_INT

protected:
#define ADD_ATTR(NS, NAME) context.add_attr(#NAME, sigc::mem_fun(*this, &YPXMLParserBase::onattr_##NS##_##NAME))
  inline void setup_handler_channel_attr(Context &context) {
    ADD_ATTR(ch, name);
    ADD_ATTR(ch, id);
    ADD_ATTR(ch, tip);
    ADD_ATTR(ch, genre);
    ADD_ATTR(ch, listeners);
    ADD_ATTR(ch, relays);
    ADD_ATTR(ch, bitrate);
    ADD_ATTR(ch, type);
    ADD_ATTR(ch, age);
    ADD_ATTR(ch, contact);
  }
  inline void setup_handler_track_attr(Context &context) {
    ADD_ATTR(track, title);
    ADD_ATTR(track, artist);
  }
  inline void setup_handler_relay_attr(Context &context) {
    ADD_ATTR(relay, listeners);
    ADD_ATTR(relay, relays);
    ADD_ATTR(relay, status);
  }
#undef ADD_ATTR

  inline YPXMLParserBase() { }
public:
  inline virtual ~YPXMLParserBase() { }
};

class YPXMLParser : public YPParser,
                    public YPXMLParserBase {
private:
  ssize_t total_;

protected:
  virtual void on_start_parsing();
  virtual void on_error(GError *error);

  void onload_chs(const AttributesWrapper &attributes);
  void onload_chs_ch(const AttributesWrapper &attributes);
  void unload_chs_ch();

public:
  inline ssize_t total() const {
    return total_;
  }

  YPXMLParser(Application           &app,
              YellowPage            &yp,
              bool                   peercast,
              std::list<RefChannel> &channel_list,
              IDHashTable<Channel>  &hash_table);
};

class YPCollection;

enum YPType {
  YPTYPE_YP4G = 0,
  YPTYPE_XML,
  YPTYPE_XML_PC,
  YPTYPE_INVALID
};
class AttributeYPType : public conf::AttributeEnum<YPType, AttributeYPType> {
  friend class conf::AttributeEnum<YPType, AttributeYPType>;
protected:
  static NameTable _name_table[];

public:
  inline AttributeYPType(const Glib::ustring &name, YPType defval) :
    conf::AttributeEnum<YPType, AttributeYPType>(name, defval)
  {
  }
};
typedef conf::RefAttributeEnum<AttributeYPType> RefAttributeYPType;

class YellowPage : public conf::Element {
  friend class conf::Collection<YellowPage, YPCollection>;
  friend class YPCollection;
public:
  enum Status {
    STATUS_NONE,
    STATUS_LOADING,
    STATUS_COMPLETE,
    STATUS_ERROR
  };

private:
  sigc::signal<void, YellowPage &, Status, Status> signal_status_changed_;
public:
  inline sigc::signal<void, YellowPage &, Status, Status>
  signal_status_changed() {
    return signal_status_changed_;
  }

private:
  Application              &app_;
  Glib::ustring             cache_dir_;

  conf::RefAttributeBool    enable_;
  conf::RefAttributeStr     icon_;
  conf::RefAttributeStr     name_;
  conf::RefAttributeStr     url_;
  conf::RefAttributeStr     encoding_;
  RefAttributeYPType        type_;
  conf::RefAttributeBool    pcraw_proxy_;
  conf::RefAttributeUint    reload_interval_;

  Status                    status_;
  loader::Pool              loader_;
  loader::RefConnection     loader_conn_;

  typedef std::list<RefChannel> ChannelList;
  ChannelList               channel_list_;
  IDHashTable<Channel>      channel_hash_;
  IDHashTable<Channel>      channel_hash_ended_;
  size_t                    chcount_;
  size_t                    chcount_valid_;

  size_t                    error_count_;
  Glib::ustring             error_message_;

  sigc::connection          conn_reload_timeout_;

  boost::posix_time::ptime  last_loaded_time_;

  void reset_reload_timeout();
  bool on_reload_timeout();

  void set_status(Status status);

  bool create_channels(const conf::Source &source);

  void loader_on_complete(const loader::RefResponse response);
  void loader_on_error(const loader::RefError error);

  Glib::ustring create_cache_path();
  bool load_cache();
  void save_cache(const conf::Source &source);

public:
  inline conf::RefAttributeBool enable() {
    return enable_;
  }
  inline conf::RefAttributeStr icon() {
    return icon_;
  }
  inline conf::RefAttributeStr name() {
    return name_;
  }
  inline conf::RefAttributeStr url() {
    return url_;
  }
  inline conf::RefAttributeStr encoding() {
    return encoding_;
  }
  inline RefAttributeYPType type() {
    return type_;
  }
  inline conf::RefAttributeBool pcraw_proxy() {
    return pcraw_proxy_;
  }
  inline conf::RefAttributeUint reload_interval() {
    return reload_interval_;
  }

  inline Status get_status() const {
    return status_;
  }
  inline loader::RefConnection &get_connection() {
    return loader_conn_;
  }

  typedef ChannelList::iterator iterator;
  inline iterator begin() {
    return channel_list_.begin();
  }
  inline iterator end() {
    return channel_list_.end();
  }
  inline RefChannel find(const ID &id) {
    return channel_hash_.lookup(id);
  }
  inline RefChannel find_ended(const ID &id) {
    return channel_hash_ended_.lookup(id);
  }

  inline size_t size() const {
    return channel_list_.size();
  }

  inline size_t get_chcount() const {
    return chcount_;
  }
  inline size_t get_chcount_valid() const {
    return chcount_valid_;
  }

  inline IDHashTable<Channel> &get_ended() {
    return channel_hash_ended_;
  }

  inline const Glib::ustring &get_error_message() const {
    return error_message_;
  }

  inline const boost::posix_time::ptime &get_last_loaded_time() const {
    return last_loaded_time_;
  }

  void reload();
  void rematch_favorite();

  YellowPage(const Glib::ustring &name,
             Application         &app,
             const Glib::ustring &cache_dir);
  virtual ~YellowPage();
};
typedef boost::shared_ptr<YellowPage> RefYellowPage;

class YPCollection : public conf::Collection<YellowPage, YPCollection> {
private:
  sigc::signal<void> signal_loaded_;
  sigc::signal<void, YellowPage &> signal_error_;
  sigc::signal<void, size_t> signal_loading_count_changed_;
public:
  inline sigc::signal<void> signal_loaded() {
    return signal_loaded_;
  }
  inline sigc::signal<void, YellowPage &> signal_error() {
    return signal_error_;
  }
  inline sigc::signal<void, size_t> signal_loading_count_changed() {
    return signal_loading_count_changed_;
  }

private:
  Application           &app_;
  Glib::ustring          cache_dir_;

  conf::RefAttributeUint time_keep_ended_;
  conf::RefAttributeBool use_cache_;

  size_t                 loading_count_;
  size_t                 chcount_;
  size_t                 chcount_valid_;

  BuiltinCommand        &bc_yp_;
  BuiltinCommand        &bc_ch_;

protected:
  virtual RefYellowPage on_create_item(const Glib::ustring &name);
  virtual void on_load_default();

  void update_chcount();
  void yp_on_status_changed(YellowPage &yp,
                            YellowPage::Status new_status,
                            YellowPage::Status old_status);

public:
  inline conf::RefAttributeBool use_cache() {
    return use_cache_;
  }
  inline conf::RefAttributeUint time_keep_ended() {
    return time_keep_ended_;
  }

  inline RefYellowPage add_item() {
    return conf::Collection<YellowPage, YPCollection>::add_item();
  }
  inline RefYellowPage add_item(const Glib::ustring &name,
                                const Glib::ustring &url,
                                const Glib::ustring &icon = Glib::ustring(),
                                YPType type               = YPTYPE_YP4G,
                                bool   enable             = true,
                                bool   pcraw_proxy        = false,
                                uint   reload_interval    = 10) {
    RefYellowPage yp      = add_item();
    yp->icon()            = icon;
    yp->name()            = name;
    yp->url()             = url;
    yp->type()            = type;
    yp->enable()          = enable;
    yp->pcraw_proxy()     = pcraw_proxy;
    yp->reload_interval() = reload_interval;
    return yp;
  }

  void load_cache();

  RefYellowPage find(const Glib::ustring &name);
  RefYellowPage find(const YellowPage *yp);
  RefChannel find(const ID &id);
  RefChannel find_ended(const ID &id);

  inline size_t get_loading_count() const {
    return loading_count_;
  }
  inline size_t get_chcount() const {
    return chcount_;
  }
  inline size_t get_chcount_valid() const {
    return chcount_valid_;
  }

  inline void reload_all() {
    for(iterator iter = begin(); iter != end(); ++iter) {
      (*iter)->reload();
    }
  }

  inline BuiltinCommand &get_bc_yp() {
    return bc_yp_;
  }
  inline BuiltinCommand &get_bc_ch() {
    return bc_ch_;
  }

  void rematch_favorite();

  void clear_ended();

private:
  bool bc_yp_reload_all(ShellLine &line, CommandOpts &opts);
  bool bc_yp_reload    (ShellLine &line, CommandOpts &opts);

  bool bc_ch_reloadyp  (ShellLine &line, CommandOpts &opts);

public:
  YPCollection(Application &app);
};
typedef boost::shared_ptr<YPCollection> RefYPCollection;

}/*gpyp*/

#endif/*YELLOWPAGE_H_*/
